package osami.communication;

import java.io.IOException;
import java.util.Vector;

//import osami.Settings;
import osami.communication.translate.HandshakeMessage;
import osami.communication.translate.Message;
import osami.communication.translate.TranslationException;
import osami.communication.translate.Translator;
import osami.communication.translate.json.JSONTranslator;
//import osami.engine.OsamiEngine;
//import osami.engine.OsamiException;
//import osami.engine.OsamiIdentifier;

//import osami.policy.*;

/**
 * CommServer handles listeners and acts as an Osami-Server.
 * 
 * @author osami
 *
 */
public class CommServer
{
	private Vector<Connection> iClientConnections;
	private Vector<ConnectionListener> iConnectionListeners;
	private int iCallNumber = 0;
	private ConnectionListenerFactory iConnectionListenerFactory = null;
	private ConnectionFactory iConnectionFactory = null;
	private Vector<Translator> iTranslators = new Vector<Translator>();
	private Activator iEngine = null;
	
	// message timeout in milliseconds
	public static final int TIMEOUT = 5000;
	
	
	/**
	 * Create the communication server object.
	 * 
	 * @throws IOException 
	 */
	public CommServer( Activator aEngine )
	{
		iEngine = aEngine;
		
		System.out.println( "CommServer.CommServer: Creating communication server object." );

		iClientConnections = new Vector<Connection>();
		iConnectionListeners = new Vector<ConnectionListener>();
		
		iConnectionListenerFactory = new ConnectionListenerFactory( iEngine, this );
		iConnectionFactory = new ConnectionFactory( iEngine, this );

		// add a new translator for JSON-messages
		iTranslators.add( new JSONTranslator() );
	}
	
	/**
	 * Creates a listener for every connection type.
	 */
	public void createListeners()
	{
		// create a TCP/IP connection listener
		if ( Boolean.parseBoolean( Settings.getInstance().getValue( Settings.TCP_IP_ENABLED ) ) )
		{
			try
			{
				addListener( ConnectionListenerFactory.TCPIP_LISTENER );
				System.out.println( "CommServer.CommServer: TCP/IP connection listener created." );
			}
			catch ( Exception aException )
			{
				System.out.println( "CommServer.CommServer: WARNING: Couldn't create TCP/IP connection listener: " + aException.getMessage() );
			}
			
			// create a TCP/IP connection multicast listener
			if ( Boolean.parseBoolean( Settings.getInstance().getValue( Settings.TCP_IP_MULTICAST ) ) )
			{
				try
				{
					addListener( ConnectionListenerFactory.MC_LISTENER );
					System.out.println( "CommServer.CommServer: TCP/IP multicast connection listener created." );
				}
				catch ( Exception aException )
				{
					System.out.println( "CommServer.CommServer: WARNING: Couldn't create Multicast connection listener: " + aException.getMessage() );
				}
			}
		}
		
		// create a bluetooth connection listener
		if ( Boolean.parseBoolean( Settings.getInstance().getValue( Settings.BLUETOOTH_ENABLED ) ) )
		{
			try
			{
				addListener( ConnectionListenerFactory.BLUETOOTH_LISTENER );
				System.out.println( "CommServer.CommServer: Bluetooth connection listener created." );
			}
			catch ( Exception aException )
			{
				System.out.println( "CommServer.CommServer: WARNING: Couldn't create Bluetooth connection listener: " + aException.getMessage() );
			}

			
			// create a bluetooth connection AdHoc listener
			if ( Boolean.parseBoolean( Settings.getInstance().getValue( Settings.BLUETOOTH_MULTICAST ) ) )
			{
				try
				{
					addListener( ConnectionListenerFactory.BT_ADHOC_LISTENER );
					System.out.println( "CommServer.CommServer: BT Ad-Hoc listener created." );
				}
				catch ( Exception aException )
				{
					System.out.println( "CommServer.CommServer: WARNING: Couldn't create BT Ad-Hoc listener: " + aException.getMessage() );
				}
			}
		}
	}

	/**
	 * Creates and adds a listener of type aType.
	 * 
	 * @param aType
	 * @throws IOException
	 * @throws OsamiException 
	 */
	private void addListener( int aType ) throws IOException, OsamiException
	{
		// create listener and add it to the listener vector
		ConnectionListener listener = iConnectionListenerFactory.createListener( aType );
		iConnectionListeners.add( listener );
	}
	
	/**
	 * Adds a new client connection.
	 * 
	 * @param aClientConnection
	 */
	public void addClient( Connection aClientConnection )
	{
		iClientConnections.add( aClientConnection );
		//sortConnectionsByPolicy();
	}

	/**
	 * Removes a client from the clients vector.
	 * 
	 * @param aClient
	 */
	public void removeClient( Connection aClient )
	{
		iClientConnections.remove( aClient );
		iEngine.removeRemoteServices( aClient.getIdentifier() );
	}
	
	/**
	 * Closes all connections.
	 */
	public void closeConnections()
	{
		// close all connections
		System.out.println( "CommServer.closeConnections: Closing " + iClientConnections.size() + " connections..." );
		while ( !iClientConnections.isEmpty() )
		{
			System.out.println( "CommServer.closeConnections: Closing " + iClientConnections.get(0).getClass().getName() + "..." );
			Connection connection = iClientConnections.get(0);
			connection.closeConnection();
			removeClient( connection );
			System.out.println( "CommServer.closeConnections: Closed." );
		}
		
		// clear connections vector
		iClientConnections.clear();
		
		System.out.println( "CommServer.closeConnections: Connections closed." );
	}
	
	/**
	 * Closes all connection listeners.
	 */
	public void closeConnectionListeners()
	{
		// close all connection listeners
		System.out.println( "CommServer.closeConnections: Closing " + iConnectionListeners.size() + " connection listeners..." );
		while( !iConnectionListeners.isEmpty() )
		{
			System.out.println( "CommServer.closeConnections: Closing listener: " + iConnectionListeners.get(0).getClass().getName() );
			iConnectionListeners.get(0).closeConnection();
			iConnectionListeners.remove(0);
			System.out.println( "CommServer.closeConnections: Listener closed." );
		}

		// clear connection listener vector
		iConnectionListeners.clear();
		
		System.out.println( "CommServer.closeConnections: Listeners closed." );
	}
	
	/**
	 * Closes all connections and connection listeners.
	 */
	public void shutdown()
	{
		closeConnectionListeners();
		closeConnections();
	}
	
	/**
	 * Creates a remote connection to an another OSAMI node. If there's already an existing
	 * connection to the same address, the existing connection will be returned. 
	 * 
	 * @param aAddress
	 * @return Connection to the remote address
	 * @throws IOException
	 * @throws TranslationException 
	 * @throws OsamiException 
	 */
	public Connection connectTo( Address aAddress ) throws IOException, TranslationException, OsamiException
	{
		// re-use existing connections when possible
		Connection connection = getConnection( aAddress );
		if ( connection != null ) return connection;

		// create connection with the connection factory
		connection = iConnectionFactory.connectTo( aAddress );

		if ( connection != null )
		{
			iClientConnections.add( connection );
			
			System.out.println( "CommServer.connectTo: Connection established." );

			// TODO: check these;
			//sortConnectionsByPolicy();

			// send handshake
			//connection.send( iEngine.getHandShake() );
		}
		else
		{
			// throw exception
			throw new IOException( "Connection couldn't be established." );
		}
		
		return connection;
	}

	/**
	 * Applies active policies to all connections.

	private void sortConnectionsByPolicy()
	{
		Vector<Policy> activePolicies = iEngine.getActivePolicies();
		
		if ( activePolicies.size()>0 ) 
		{
			for ( Policy acPolicy: activePolicies )
			{
				if ( acPolicy.getType().contentEquals( "Communication" ) )
					acPolicy.execute();
			}	
		}		
	}
	 */
	/**
	 * Returns a connection based on the given address if one is found from the connections vector.
	 * 
	 * @param aAddress
	 * @return Connection to the remote address if one is found.
	 */
	public Connection getConnection( Address aAddress )
	{
		for ( Connection connection: iClientConnections )
		{
			if ( connection.getAddress().equals( aAddress ) )
				return connection;
		}
		
		return null;
	}
	
	/**
	 * Sends a message to one of the clients. The message has to define the receiver's ID.
	 * 
	 * @param aMessage
	 * @return Response message
	 * @throws TranslationException
	 * @throws OsamiException 
	 */
	public Message send( Message aMessage ) throws TranslationException, OsamiException
	{
		System.out.println( "CommServer.send: sending a message to " + aMessage.getDestination() );
		
		for ( Connection connection : iClientConnections )
		{
			if ( connection.getIdentifier().getID() == aMessage.getDestination().getID() )
				return connection.send( aMessage );
		}
		
		throw new OsamiException( "ERROR: destination connection with id " + aMessage.getDestination() + " was not found." );
	}
	
	/**
	 * Sends a message to a connected node with ID aIdentifier.
	 * 
	 * @param aMessage
	 * @param aIdentifier
	 * @throws TranslationException
	 * @throws OsamiException 
	 */
	public void send( Message aMessage, OsamiIdentifier aIdentifier ) throws TranslationException, OsamiException
	{
		for ( int i = 0; i < iClientConnections.size(); i++ )
		{
			OsamiIdentifier identifier = iClientConnections.elementAt(i).getIdentifier();
			
			if ( identifier.getID() == aIdentifier.getID() )
			{
				iClientConnections.elementAt(i).send( aMessage );
				return;
			}
		}
		
		throw new OsamiException( "Could not send a message because the destination connection to node " + aIdentifier + " was not found!" );
	}
	
	/**
	 * Returns a vector containing all connections.
	 * 
	 * @return Vector<Connection>
	 */
	public Vector<Connection> getConnections()
	{
		return iClientConnections;
	}
	
	/**
	 * Handles the handshake protocol. The handshake message is always in JSON-
	 * format, and it contains the ID of it's sender and a list of translators
	 * the sender supports. If parameter aRespond is true, a response handshake
	 * message is sent to the sender.
	 * 
	 * @param aHandshakeString
	 * @param aConnection
	 * @param aRespond
	 * @return OsamiIdentifier identifying the sender of the handshake message
	 * @throws TranslationException 
	 * @throws OsamiException 
	 */
	public OsamiIdentifier handleHandshake( String aHandshakeString, Connection aConnection, boolean aRespond ) throws TranslationException, OsamiException
	{
		// handshake-message is always in json-format
		HandshakeMessage handshake = (HandshakeMessage)getTranslator( "json" ).translate( aHandshakeString );

		// set connection's translator
		String preferredTranslator = handshake.getTranslators().elementAt(0);
		aConnection.setTranslator( getTranslator( preferredTranslator ) );
		
		if ( aRespond )
		{
			// send handshake response
			System.out.println( "CommServer.handleHandshake: returning a handshake." );
			aConnection.send( iEngine.getHandShake() );
		}

		// return sender's ID
		return handshake.getSender();
	}

	/**
	 * Returns a new call number and increments the internal call counter of this object.
	 * 
	 * @return int
	 */
	public int getUniqueCallNumber()
	{
		return iCallNumber++;
	}
	
	/**
	 * Send a message to all neighboring nodes.
	 * 
	 * @throws TranslationException 
	 * 
	 */
	public void sendAll( Message aMessage ) throws TranslationException
	{
		for ( int i = 0; i < iClientConnections.size(); i++ )
		{
			Connection connection = iClientConnections.elementAt(i);
			connection.send( aMessage );
		}
	}
	
	/**
	 * Returns a translator object of given type, or throws an OsamiException
	 * if none is found.
	 * 
	 * @param aName
	 * @return Translator object, if one is found by the given name.
	 * @throws OsamiException
	 */
	public Translator getTranslator( String aName ) throws OsamiException
	{
		for ( Translator translator : iTranslators )
			if ( translator.getName().contentEquals( aName ) )
				return translator;
		
		throw new OsamiException( "Translator not found: '" + aName + "'" );
	}

	/**
	 * Returns all supported translators.
	 * 
	 * @return Vector<Translator>
	 */
	public Vector<Translator> getTranslators()
	{
		return iTranslators;
	}

	/**
	 * Set's the connections-vector.
	 * 
	 * @param aClientConnections
	 */
	public void setConnections( Vector<Connection> aClientConnections )
	{
		iClientConnections  = aClientConnections;
	}
}
